package rexsee.service;

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.sql.Date;
import java.text.SimpleDateFormat;

import rexsee.core.alarm.RexseeAlarm;
import rexsee.core.application.RexseeApplication;
import rexsee.core.browser.Browser;
import rexsee.core.browser.clazz.JavascriptInterface;
import rexsee.core.browser.clazz.UrlListener;
import rexsee.core.lang.RexseeLanguage;
import rexsee.core.utilities.Escape;
import android.content.Context;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Handler;

public class RexseeReliableSubmit implements JavascriptInterface {

	public static final String INTERFACE_NAME = "ReliableSubmit";
	@Override
	public String getInterfaceName() {
		return mBrowser.application.resources.prefix + INTERFACE_NAME;
	}
	@Override
	public JavascriptInterface getInheritInterface(Browser childBrowser) {
		return this;
	}
	@Override
	public JavascriptInterface getNewInterface(Browser childBrowser) {
		return new RexseeReliableSubmit(childBrowser);
	}

	public static final String DATABASE_RELIABLESUBMIT = "reliableSubmit.db";
	public static final String TABLE_MESSAGE = "message";
	public static final String TABLE_ARGUMENTS = "arguments";

	public static final int STATUS_WAIT = 0;
	public static final int STATUS_DONE = 1;
	public static final int STATUS_EXPIRED = 2;

	public static final int DEFAULT_TRIESDURATION = 5;
	public static final int DEFAULT_TRIESTIMEOUT = 30;

	public static final String LINEEND = "\r\n";
	public static final String DOUBLEHYPHENS = "--";
	public static final String BOUNDARY = "*****";

	private final Browser mBrowser;
	private final Context mContext;
	private final Handler submitHandler;
	private Runnable submitRunnable = null;

	public RexseeReliableSubmit(Context context) {
		mBrowser = null;
		mContext = context;
		submitHandler = new Handler();
		initDatabase(mContext);
	}
	public RexseeReliableSubmit(Browser browser) {
		mBrowser = browser;
		mContext = mBrowser.getContext();
		submitHandler = new Handler();
		initDatabase(mContext);
		browser.urlListeners.add(new UrlListener("submit://") {
			@Override
			public void run(Context context, Browser browser, String url) {
				SQLiteDatabase db = mContext.openOrCreateDatabase(DATABASE_RELIABLESUBMIT, Context.MODE_PRIVATE, null);
				String html = "<HTML><HEAD><TITLE>" + url + "</TITLE></HEAD>";
				html += "<BODY style='margin:0px;background-color:black;color:white;'>";
				if (db != null) {
					try {
						String path = Uri.parse(url).getSchemeSpecificPart();
						if (path == null || path.equals("") || path.substring(path.lastIndexOf("/") + 1).equals("")) {
							html += "<table width=100% cellspacing=0 style='color:white;font-size:16px;'>";
							Cursor cursor = db.rawQuery("SELECT * from " + TABLE_MESSAGE + ";", null);
							if (cursor.getCount() == 0) {
								html += "<tr><td><div style='padding:10px;line-height:150%;'>Message not found.</div></td></tr>";
							} else {
								for (int i = cursor.getCount() - 1; i >= 0; i--) {
									cursor.moveToPosition(i);
									String datetime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(cursor.getLong(1)));
									int status = cursor.getInt(10);
									if (status == STATUS_EXPIRED) {
										html += "<tr style='background-color:#444444;color:red;'>";
									} else if (status == STATUS_DONE) {
										html += "<tr style='color:yellow'>";
									} else {
										html += "<tr>";
									}
									html += "<td onclick=\"" + browser.function.getInterfaceName() + ".go('submit://" + cursor.getString(0) + "');\" style='border-bottom:1px solid #222222; padding:10 5 10 5;'>" +
													"<div>" + cursor.getString(0) + "</div>" +
													"<div style='font-size:14px;color:gray;margin-top:5px;'>" + datetime + "</div>" +
											"</td>";
									html += "</tr>";
								}
							}
							cursor.close();
							html += "</table>";
						} else {
							String key = path.substring(path.lastIndexOf("/") + 1);
							Cursor cursor = db.rawQuery("SELECT * from " + TABLE_MESSAGE + " where key='" + key + "' limit 1;", null);
							if (cursor.getCount() != 1) {
								html += "<BODY style='margin:0px;background-color:black;color:white;'>";
								html += "<div style='padding:10px;line-height:150%;'>Message " + key + " not found.</div>";
							} else {
								cursor.moveToFirst();
								html += "<BODY style='margin:0px;background-color:black;color:white;'>";
								html += "<table width=100% cellspacing=5 cellpadding=0 style='color:white;font-size:14px;'>";
								html += "<tr height=10px><td></td><td></td></tr>";
								html += "<tr><td nowrap valign=top>Key:</td><td>" + cursor.getString(0) + "</td></tr>";
								html += "<tr><td nowrap valign=top>Date:</td><td>" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(cursor.getLong(1))) + "</td></tr>";
								html += "<tr><td nowrap valign=top>URL:</td><td>" + cursor.getString(2) + "</td></tr>";
								html += "<tr><td nowrap valign=top>Encoding:</td><td>" + cursor.getString(3) + "</td></tr>";
								html += "<tr><td nowrap valign=top>Message:</td><td>" + cursor.getString(4) + "</td></tr>";
								html += "<tr><td nowrap valign=top>Priority:</td><td>" + cursor.getInt(5) + "</td></tr>";
								html += "<tr><td nowrap valign=top>Expire Date:</td><td>" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(cursor.getLong(6))) + "</td></tr>";
								html += "<tr><td nowrap valign=top>Expire Action:</td><td>" + cursor.getString(7) + "</td></tr>";
								html += "<tr><td nowrap valign=top>Tries:</td><td>" + cursor.getInt(8) + " times</td></tr>";
								html += "<tr><td nowrap valign=top>Last Try:</td><td>" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(cursor.getLong(9))) + "</td></tr>";
								int status = cursor.getInt(10);
								html += "<tr><td nowrap>Status:</td><td>" + ((status == STATUS_WAIT) ? "wait" : ((status == STATUS_DONE) ? "done" : "expired")) + "</td></tr>";
								html += "<tr><td nowrap valign=top>Response:</td><td>" + Escape.unescape(cursor.getString(11)) + "</td></tr>";
								html += "<tr height=35px><td colspan=2 valign=bottom style='border-top:1px solid #222222; padding:10 5 10 0;font-size:16px;'>";
								html += "<a href='" + browser.application.resources.prefix + ":' onclick=\"javascript:" + getInterfaceName() + ".remove('" + cursor.getString(0) + "');" + browser.function.getInterfaceName() + ".goBack();\" style='color:white;text-decoration:underline;'>" + RexseeLanguage.ACTION_DELETE + "</a>";
								if (status == STATUS_WAIT) html += "&nbsp;|&nbsp;<a href='" + browser.application.resources.prefix + ":' onclick=\"javascript:" + getInterfaceName() + ".tries('" + cursor.getString(0) + "');" + browser.function.getInterfaceName() + ".reload();\" style='color:white;text-decoration:underline;'>" + RexseeLanguage.ACTION_SUBMIT + "</a>";
								html += "</td></tr>";
								html += "</table>";
							}
							cursor.close();
						}
					} catch (Exception e) {
						db.close();
						html += "<div style='color:white;font-size:16px;padding:10px;'>Database Error: " + e.getLocalizedMessage() + "</div>";
					}
				} else {
					html += "<div style='color:white;font-size:16px;padding:10px;'>Database Error: Can't open database.</div>";
				}
				html += "</BODY>";
				html += "</HTML>";
				db.close();
				browser.function.loadHTMLWithoutHistory(html);
			}
			@Override
			public boolean shouldAddToHistory(Context context, Browser browser, String url) {
				return true;
			}
		});
	}

	private static void initDatabase(Context context) {
		SQLiteDatabase db = context.openOrCreateDatabase(DATABASE_RELIABLESUBMIT, Context.MODE_PRIVATE, null);
		db.execSQL("CREATE TABLE if not exists " + TABLE_ARGUMENTS + " (duration int,timeout int);");
		db.execSQL("CREATE TABLE if not exists " + TABLE_MESSAGE + " ("
				+ "key Text, " //name of the message
				+ "date long, " //time stamp, current time
				+ "url Text, " //Submit URL
				+ "encoding Text, " //encoding of the response
				+ "message Text, " //Submit message
				+ "priority int, " //integer
				+ "expireDate long, " //time stamp, expire time
				+ "expireAction Text, " //Notification arguments
				+ "tries int, " // Times already tried
				+ "lastTry long, " // time stamp, time of last try
				+ "status int, " // Status, wait, done or expired
				+ "response Text, " //Notification arguments
				+ "Primary key(key));");
		db.close();
	}
	public void register() {
		submitRunnable = new Runnable() {
			@Override
			public void run() {
				try {
					tries();
					submitHandler.postDelayed(submitRunnable, getDuration() * 1000 * 60);
				} catch (Exception e) {
				}
			}
		};
		submitHandler.postDelayed(submitRunnable, 1000);
	}

	public long size() {
		try {
			SQLiteDatabase db = mContext.openOrCreateDatabase(DATABASE_RELIABLESUBMIT, Context.MODE_PRIVATE, null);
			if (db == null) return -1;
			long rtn;
			rtn = DatabaseUtils.queryNumEntries(db, TABLE_MESSAGE);
			db.close();
			return rtn;
		} catch (Exception e) {
			return -1;
		}
	}
	public boolean exists(String key) {
		if (key == null || key.equals("")) return false;
		try {
			SQLiteDatabase db = mContext.openOrCreateDatabase(DATABASE_RELIABLESUBMIT, Context.MODE_PRIVATE, null);
			Cursor cursor = db.rawQuery("SELECT key from " + TABLE_MESSAGE + " WHERE key='" + key + "';", null);
			if (cursor != null && cursor.getCount() > 0) {
				cursor.close();
				db.close();
				return true;
			} else {
				try {
					cursor.close();
				} catch (Exception e) {

				}
				db.close();
				return false;
			}
		} catch (Exception e) {
			return false;
		}
	}
	public String get(String key) {
		if (key == null || key.equals("")) return "";
		try {
			SQLiteDatabase db = mContext.openOrCreateDatabase(DATABASE_RELIABLESUBMIT, Context.MODE_PRIVATE, null);
			Cursor cursor = db.rawQuery("SELECT * from " + TABLE_MESSAGE + " WHERE key='" + key + "';", null);
			String rtn = "";
			if (cursor != null && cursor.getCount() > 0) {
				cursor.moveToFirst();
				rtn += "{";
				rtn += "\"key\":\"" + cursor.getString(0) + "\"";
				rtn += ",\"date\":" + cursor.getLong(1);
				rtn += ",\"url\":\"" + cursor.getString(2) + "\"";
				rtn += ",\"encoding\":\"" + cursor.getString(3) + "\"";
				rtn += ",\"message\":\"" + cursor.getString(4) + "\"";
				rtn += ",\"priority\":" + cursor.getInt(5);
				rtn += ",\"expireDate\":" + cursor.getLong(6);
				rtn += ",\"expireAction\":\"" + cursor.getString(7) + "\"";
				rtn += ",\"tries\":" + cursor.getInt(8);
				rtn += ",\"lastTry\":" + cursor.getLong(9);
				int status = cursor.getInt(10);
				rtn += ",\"status\":\"" + ((status == STATUS_WAIT) ? "wait" : ((status == STATUS_DONE) ? "done" : "expired")) + "\"";
				rtn += ",\"response\":\"" + cursor.getString(11) + "\"";
				rtn += "}";
				cursor.close();
			}
			db.close();
			return rtn;
		} catch (Exception e) {
			return "";
		}
	}

	public String getKeys(int status, boolean byPriority) {
		try {
			SQLiteDatabase db = mContext.openOrCreateDatabase(DATABASE_RELIABLESUBMIT, Context.MODE_PRIVATE, null);
			Cursor cursor;
			if (byPriority) {
				if (status < 0) {
					cursor = db.rawQuery("SELECT key from " + TABLE_MESSAGE + " ORDER BY priority DESC;", null);
				} else {
					cursor = db.rawQuery("SELECT key from " + TABLE_MESSAGE + " WHERE status=" + status + " ORDER BY priority DESC;", null);
				}
			} else {
				if (status < 0) {
					cursor = db.rawQuery("SELECT key from " + TABLE_MESSAGE + ";", null);
				} else {
					cursor = db.rawQuery("SELECT key from " + TABLE_MESSAGE + " WHERE status=" + status + ";", null);
				}
			}
			String rtn = "[";
			if (cursor != null && cursor.getCount() > 0) {
				for (int i = 0; i < cursor.getCount(); i++) {
					if (i != 0) rtn += ",";
					cursor.moveToPosition(i);
					rtn += "\"" + cursor.getString(0) + "\"";
				}
				cursor.close();
			}
			rtn += "]";
			db.close();
			return rtn;
		} catch (Exception e) {
			return "[]";
		}
	}
	public String getKeys() {
		return getKeys(-1, true);
	}
	public String getWaitingKeys() {
		return getKeys(STATUS_WAIT, true);
	}
	public String getDoneKeys() {
		return getKeys(STATUS_WAIT, true);
	}
	public String getExpiredKeys() {
		return getKeys(STATUS_WAIT, true);
	}

	public void remove(String key) {
		if (key == null || key.equals("")) return;
		try {
			SQLiteDatabase db = mContext.openOrCreateDatabase(DATABASE_RELIABLESUBMIT, Context.MODE_PRIVATE, null);
			db.execSQL("DELETE FROM " + TABLE_MESSAGE + " where key='" + key + "';");
			db.close();
		} catch (Exception e) {
		}
	}
	private void remove(int status) {
		try {
			SQLiteDatabase db = mContext.openOrCreateDatabase(DATABASE_RELIABLESUBMIT, Context.MODE_PRIVATE, null);
			db.execSQL("DELETE FROM " + TABLE_MESSAGE + " where status=" + status + ";");
			db.close();
		} catch (Exception e) {
		}
	}
	public void removeWaiting() {
		remove(STATUS_WAIT);
	}
	public void removeDone() {
		remove(STATUS_DONE);
	}
	public void removeExpired() {
		remove(STATUS_EXPIRED);
	}

	public boolean add(String key, int priority, String url, String encoding, String message, long expireTime, String expireAction) {
		url = mBrowser.urlListeners.getAbsoluteUrl(url);
		String scheme = Uri.parse(url).getScheme();
		if (!scheme.equals("http") && !scheme.equals("https")) {
			mBrowser.exception(getInterfaceName(), "Wrong Scheme!");
			return false;
		}
		if (!mBrowser.domainWhiteList.isValidUrl(url)) {
			mBrowser.exception(getInterfaceName(), "Url not allowed.");
			return false;
		}
		if (key == null || key.equals("")) return false;
		try {
			SQLiteDatabase db = mContext.openOrCreateDatabase(DATABASE_RELIABLESUBMIT, Context.MODE_PRIVATE, null);
			Cursor cursor = db.rawQuery("SELECT * from " + TABLE_MESSAGE + " WHERE key='" + key + "';", null);
			if (cursor == null || cursor.getCount() == 0) {
				long time = System.currentTimeMillis();
				long expire = time + expireTime * 1000 * 60;
				String sql = "INSERT INTO " + TABLE_MESSAGE + " VALUES ('" + key + "', " + time + ",  '" + url + "', '" + encoding + "', '" + message + "', " + priority + ", " + expire + ", '" + expireAction + "', 0,0," + STATUS_WAIT + ",'');";
				db.execSQL(sql);
				try {
					cursor.close();
				} catch (Exception e) {

				}
				db.close();
				return true;
			} else {
				cursor.close();
				db.close();
				return false;
			}
		} catch (Exception e) {
			return false;
		}
	}

	public void setDurationAndTimeout(int durationMinutes, int timeoutSeconds) {
		try {
			SQLiteDatabase db = mContext.openOrCreateDatabase(DATABASE_RELIABLESUBMIT, Context.MODE_PRIVATE, null);
			db.execSQL("DELETE FROM " + TABLE_ARGUMENTS + ";");
			db.execSQL("INSERT INTO " + TABLE_ARGUMENTS + " VALUES (" + durationMinutes + "," + timeoutSeconds + ");");
			db.close();
		} catch (Exception e) {
		}
	}
	public int getDuration() {
		try {
			SQLiteDatabase db = mContext.openOrCreateDatabase(DATABASE_RELIABLESUBMIT, Context.MODE_PRIVATE, null);
			Cursor cursor = db.rawQuery("SELECT duration from " + TABLE_ARGUMENTS + ";", null);
			int rtn;
			if (cursor == null || cursor.getCount() == 0) {
				rtn = DEFAULT_TRIESDURATION;
			} else {
				cursor.moveToFirst();
				rtn = cursor.getInt(0);
			}
			try {
				cursor.close();
			} catch (Exception e) {

			}
			db.close();
			return rtn;
		} catch (Exception e) {
			return DEFAULT_TRIESDURATION;
		}
	}
	public int getTimeout() {
		try {
			SQLiteDatabase db = mContext.openOrCreateDatabase(DATABASE_RELIABLESUBMIT, Context.MODE_PRIVATE, null);
			Cursor cursor = db.rawQuery("SELECT timeout from " + TABLE_ARGUMENTS + ";", null);
			int rtn;
			if (cursor == null || cursor.getCount() == 0) {
				rtn = DEFAULT_TRIESTIMEOUT;
			} else {
				cursor.moveToFirst();
				rtn = cursor.getInt(0);
			}
			try {
				cursor.close();
			} catch (Exception e) {

			}
			db.close();
			return rtn;
		} catch (Exception e) {
			return DEFAULT_TRIESTIMEOUT;
		}
	}

	private String triesToSubmit(String key, String postUrl, String postData, int timeout, String encoding) {
		try {
			URL url = new URL(postUrl);
			HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
			urlConnection.setRequestMethod("POST");
			urlConnection.setDoInput(true);
			urlConnection.setDoOutput(true);
			urlConnection.setConnectTimeout(timeout);
			urlConnection.setReadTimeout(timeout);
			urlConnection.setUseCaches(false);
			urlConnection.setRequestProperty("Connection", "Keep-Alive");
			if (postData.toLowerCase().startsWith("file://")) {
				String fileName = postData.substring(postData.lastIndexOf("/") + 1);
				Uri uri = Uri.parse(postData);
				final File file = new File(uri.getPath());
				if (!file.exists() || !file.isFile()) return "File error: File not found.";
				if (!file.canRead()) return "File error: File not readable.";
				urlConnection.setRequestMethod("POST");
				urlConnection.setDoInput(true);
				urlConnection.setDoOutput(true);
				urlConnection.setChunkedStreamingMode(2 * 1024 * 1024);
				urlConnection.setRequestProperty("User-Agent", RexseeApplication.getUserAgent(mContext));
				urlConnection.setRequestProperty("Connection", "Keep-Alive");
				urlConnection.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + BOUNDARY);
				urlConnection.connect();
				DataOutputStream dos = new DataOutputStream(urlConnection.getOutputStream());
				dos.writeBytes(DOUBLEHYPHENS + BOUNDARY + LINEEND);
				dos.writeBytes("Content-Disposition: form-data; name='" + key + "'; filename='" + fileName + "'" + LINEEND);
				dos.writeBytes(LINEEND);
				FileInputStream fileInputStream = new FileInputStream(file);
				int uploadedSize = 0;
				byte[] buffer = new byte[1024];
				int bufferLength = 0;
				while ((bufferLength = fileInputStream.read(buffer)) > 0) {
					dos.write(buffer, 0, bufferLength);
					uploadedSize += bufferLength;
				}
				dos.writeBytes(LINEEND);
				dos.writeBytes(DOUBLEHYPHENS + BOUNDARY + DOUBLEHYPHENS + LINEEND);
				fileInputStream.close();
				dos.flush();
				dos.close();
			} else {
				byte[] postBytes = postData.getBytes();
				urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
				urlConnection.setRequestProperty("Content-Length", "" + postBytes.length);
				urlConnection.connect();
				DataOutputStream dos = new DataOutputStream(urlConnection.getOutputStream());
				dos.write(postBytes);
				dos.flush();
				dos.close();
			}
			InputStream inputStream = urlConnection.getInputStream();
			byte[] buffer = new byte[urlConnection.getContentLength()];
			int bufferLength = inputStream.read(buffer);
			inputStream.close();
			urlConnection.disconnect();
			return (bufferLength > 0) ? new String(buffer, encoding) : null;
		} catch (Exception e) {
			return null;
		}
	}
	public void triesToSubmit(final String sql) {
		new Thread() {
			@Override
			public void run() {
				if (mBrowser != null) mBrowser.progressDialog.show(RexseeLanguage.PROGRESS_ONGOING);
				try {
					SQLiteDatabase db = mContext.openOrCreateDatabase(DATABASE_RELIABLESUBMIT, Context.MODE_PRIVATE, null);
					Cursor cursor = db.rawQuery(sql, null);
					if (cursor == null) {
						db.close();
						return;
					}
					for (int i = 0; i < cursor.getCount(); i++) {
						cursor.moveToPosition(i);
						if (cursor.getInt(10) == STATUS_WAIT) {
							String key = cursor.getString(0);
							if (System.currentTimeMillis() > cursor.getLong(6)) {
								db.execSQL("UPDATE " + TABLE_MESSAGE + " SET status=" + String.valueOf(STATUS_EXPIRED) + "  WHERE key='" + key + "';");
								(new RexseeAlarm(mContext)).set(cursor.getString(7));
							} else {
								int tries = cursor.getInt(8) + 1;
								String response = triesToSubmit(cursor.getString(0), cursor.getString(2), cursor.getString(4), getTimeout() * 1000, cursor.getString(3));
								if (response != null && !response.equals("")) {
									if (response.startsWith("File error:")) {
										db.execSQL("UPDATE " + TABLE_MESSAGE + " SET status=" + String.valueOf(STATUS_EXPIRED) + ", response='" + Escape.escape(response) + "'  WHERE key='" + key + "';");
										(new RexseeAlarm(mContext)).set(cursor.getString(7));
									} else {
										db.execSQL("UPDATE " + TABLE_MESSAGE + " SET tries=" + String.valueOf(tries) + ", lastTry=" + String.valueOf(System.currentTimeMillis()) + ", status=" + String.valueOf(STATUS_DONE) + ", response='" + Escape.escape(response) + "'  WHERE key='" + key + "';");
										(new RexseeAlarm(mContext)).set(response);
									}
								} else {
									db.execSQL("UPDATE " + TABLE_MESSAGE + " SET tries=" + String.valueOf(tries) + ", lastTry=" + String.valueOf(System.currentTimeMillis()) + " WHERE key='" + key + "';");
								}
							}
						}
					}
					try {
						cursor.close();
					} catch (Exception e) {

					}
					db.close();
				} catch (Exception e) {
					if (mBrowser != null) mBrowser.exception(getInterfaceName(), e);
				}
				if (mBrowser != null) {
					mBrowser.progressDialog.hide();
					mBrowser.application.alert(RexseeLanguage.PROGRESS_REFRESH_FINISH);
				}
			}
		}.start();
	}
	public void tries(final String key) {
		if (key == null || key.equals("")) return;
		triesToSubmit("SELECT * from " + TABLE_MESSAGE + " WHERE key='" + key + "';");
	}
	public void tries() {
		triesToSubmit("SELECT * from " + TABLE_MESSAGE + " WHERE status=" + STATUS_WAIT + " ORDER BY priority DESC;");
	}

}
